package de.hpi.generator.generator.storages;

import java.io.IOException;

import de.hpi.generator.helpers.LargeDataOfIntegersStorage;
import java.io.DataOutput;
import java.io.RandomAccessFile;

public class UniqueValuesDataStorage extends ValuesDataStorage {

    protected LargeDataOfIntegersStorage aFastDataAccessor = null;
     private final int SAVED_ELEMENTS_SIZE_IN_BYTES = 8;

    public UniqueValuesDataStorage(String aDataFilePath) {
        super(aDataFilePath);
        aFastDataAccessor = new LargeDataOfIntegersStorage(String.format("%s_LDS", aDataFilePath));
    }

    /**
     * appends aValueSeriaInfo to a unique values data storage
     * the first value - aValue
     * the second value - aValueCount
     * UniqueValuesIndex will be setted as aWritedValuesCount
     * @param aValueSeriaInfo a data to be written
     * @return aValueSeriaInfo which was written with aWritedValuesCount as UniqueValuesIndex
     * @throws Exception 
     */
    public ValueSeriaInfo appendNewValueSeriaInfo(ValueSeriaInfo aValueSeriaInfo) throws Exception {
        ValueSeriaInfo result = new ValueSeriaInfo(aValueSeriaInfo.getValue(), aValueSeriaInfo.getValueCount());

        int aValue = result.getValue();
        result.setUniqueValueIndex(getPositionOfValue(aValue));

        if (result.getUniqueValueIndex() == -1) {
            
            writeValueSeriaInfo(aDataOutputStream, result);
            result.setUniqueValueIndex(aWritedValuesCount);

            aWritedValuesCount++;
            aFastDataAccessor.setValueByIndex(aWritedValuesCount, getIndexOfValue(aValue));
        }
        return result;
    }

    private void writeValueSeriaInfo(DataOutput aOutStream,  ValueSeriaInfo aValueSeriaInfo) throws IOException {
        aOutStream.writeInt(aValueSeriaInfo.getValue());
        aOutStream.writeInt(aValueSeriaInfo.getValueCount());
    }

    public ValueSeriaInfo updateValueSeriaInfo(ValueSeriaInfo aValueSeriaInfo) throws Exception {
        if (isOpenForAppend()) {
            throw new Exception("Close the storage before update data");
        }

        long aValueSeriaInfoOffsetInBytes = calculateByteOffsetInFileForValueSeriaInfo(aValueSeriaInfo);

        RandomAccessFile aStreamToWrite = new RandomAccessFile(aDataFilePath, "rw");
        aStreamToWrite.seek(aValueSeriaInfoOffsetInBytes);

        writeValueSeriaInfo(aStreamToWrite, aValueSeriaInfo);
        aStreamToWrite.close();

        return aValueSeriaInfo;
    }

    protected long calculateByteOffsetInFileForValueSeriaInfo(ValueSeriaInfo aValueSeriaInfo) {
        return aValueSeriaInfo.getUniqueValueIndex() * SAVED_ELEMENTS_SIZE_IN_BYTES;
    }
    
    public int getPositionOfValue(int aValue) throws Exception {
        return aFastDataAccessor.getValueByIndex(getIndexOfValue(aValue)) - 1;
    }

    private long getIndexOfValue(int aValue) {
        return ((long) aValue + Math.abs((long) Integer.MIN_VALUE));
    }

    /**
     * Reads an entry from a binary unique values data storage
     * 
     * @return aUniqueValuesDataEntry which consists from a value of the integer
     *         data, that should be saved to the data storage and a counter of
     *         the value data, that shows how many times the value should be
     *         written to the data storage
     * @throws IOException
     */
    public ValueSeriaInfo readNextValueSeriaInfo() throws IOException, Exception {
        ValueSeriaInfo aValueSeriaInfo = new ValueSeriaInfo(
                aDataInputStream.readInt(),
                aDataInputStream.readInt());

        aValueSeriaInfo.setUniqueValueIndex(aReadedValuesCount);
        aReadedValuesCount++;

        return aValueSeriaInfo;
    }

    @Override
    public void closeAfterAppend() throws Exception {
        super.closeAfterAppend();
        aFastDataAccessor.clean();
    }

    /**
     * Checks if sequence of values was generated
     * 
     * @param aValue
     *            value to check
     * @return true if sequence of values was generated, otherwise false
     */
    public boolean isSeriaOfValueWasNotDistributed(ValueSeriaInfo aValueSeriaInfo) throws Exception {
        return (getPositionOfValue(aValueSeriaInfo.getValue()) == -1);
    }

}
